using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;

namespace ViewOn.UI.Control
{
    public class VirtualizingVerticalWrapPanel : VirtualizingPanel, IScrollInfo
    {
        public VirtualizingVerticalWrapPanel()
        {
            renderTransform = new TranslateTransform();
            this.RenderTransform = renderTransform;
        }
        protected override System.Windows.Size MeasureOverride(
          Size constraint)
        {
            // How big an area have we got to play with?
            bool invalidate = (viewport != constraint);
            viewport = constraint;

            // How big would things like to be? Note - this is "rough"
            // because it changes when we animate the current picture.
            DetermineStandardItemSize();

            // How big is our entire area then?
            int itemsPerLine = ItemsPerLine;

            Size newExtent = new Size(constraint.Width,
              Math.Ceiling((double)ItemCount / (double)itemsPerLine) * itemHeight);

            invalidate = (invalidate) || (newExtent != extent);
            extent = newExtent;

            RealizeVisibleItems();

            foreach (UIElement element in this.InternalChildren)
            {
                element.Measure(maxSize);
            }

            if (invalidate && (scrollOwner != null))
            {
                scrollOwner.InvalidateScrollInfo();
            }
            return (constraint);
        }
        private int ItemsPerLine
        {
            get
            {
                return ((int)(viewport.Width / itemWidth));
            }
        }
        private int FirstItemVisibleInViewport
        {
            get
            {
                int firstItem =
                  (int)((verticalOffset / itemHeight) * ItemsPerLine);

                return (firstItem);
            }
        }
        private int LastItemVisibleInViewport
        {
            get
            {
                double lines = (viewport.Height / itemHeight);
                double items = lines * ItemsPerLine;

                int lastItem = FirstItemVisibleInViewport +
                  (int)Math.Ceiling(items);

                lastItem--;

                lastItem = Math.Min(lastItem, ItemCount - 1);

                return (lastItem);
            }
        }
        private void RealizeVisibleItems()
        {
            IItemContainerGenerator generator = this.ItemContainerGenerator;

            int i = 0;

            for (i = FirstItemVisibleInViewport;
              i <= LastItemVisibleInViewport; i++)
            {
                GeneratorPosition pos = generator.GeneratorPositionFromIndex(i);

                // Should be visible
                if (pos.Offset != 0)
                {
                    // Virtualised so we need to fix.
                    using (generator.StartAt(pos, GeneratorDirection.Forward))
                    {
                        UIElement element = generator.GenerateNext() as UIElement;
                        generator.PrepareItemContainer(element);

                        pos = generator.GeneratorPositionFromIndex(i);

                        this.InsertInternalChild(pos.Index, element);
                    }
                }
            }
            i = FirstItemVisibleInViewport - 1;

            while (i >= 0)
            {
                GeneratorPosition pos = generator.GeneratorPositionFromIndex(i);

                if (pos.Offset == 0)
                {
                    this.RemoveInternalChildRange(pos.Index, 1);
                    generator.Remove(pos, 1);
                }
                else
                {
                    break;
                }
                i--;
            }

            i = LastItemVisibleInViewport + 1;

            while (i < this.ItemCount)
            {
                GeneratorPosition pos = generator.GeneratorPositionFromIndex(i);

                if (pos.Offset == 0)
                {
                    this.RemoveInternalChildRange(pos.Index, 1);
                    generator.Remove(pos, 1);
                }
                else
                {
                    break;
                }
                i++;
            }
        }
        private void RealizeFirstItem()
        {
            IItemContainerGenerator generator = this.ItemContainerGenerator;
            GeneratorPosition pos = generator.GeneratorPositionFromIndex(0);

            using (generator.StartAt(pos, GeneratorDirection.Forward))
            {
                UIElement element = generator.GenerateNext() as UIElement;

                generator.PrepareItemContainer(element);

                this.AddInternalChild(element);
            }
        }
        private void DetermineStandardItemSize()
        {
            if ((itemWidth == 0) || (itemHeight == 0))
            {
                if (this.InternalChildren.Count == 0)
                {
                    RealizeFirstItem();
                }
                UIElement element = this.InternalChildren[0];
                element.Measure(maxSize);

                itemWidth = element.DesiredSize.Width;
                itemHeight = element.DesiredSize.Height;
            }
        }
        private int ItemCount
        {
            get
            {
                int count = 0;

                ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);

                if ((itemsControl != null) && (itemsControl.Items != null))
                {
                    count = itemsControl.Items.Count;
                }
                return (count);
            }
        }
        protected override System.Windows.Size ArrangeOverride(
          System.Windows.Size finalSize)
        {
            double xPos = 0.0d;
            double yPos = verticalOffset;
            double maxHeightOnLine = 0.0d;

            foreach (UIElement element in this.InternalChildren)
            {
                Rect itemSize = new Rect(element.DesiredSize);

                if ((xPos + itemSize.Width) > finalSize.Width)
                {
                    xPos = 0.0d;
                    yPos = yPos + maxHeightOnLine;
                    maxHeightOnLine = 0.0d;
                }
                element.Arrange(new Rect(xPos, yPos,
                  itemSize.Width, itemSize.Height));

                maxHeightOnLine = Math.Max(maxHeightOnLine, itemSize.Height);

                xPos += itemSize.Width;
            }
            return (finalSize);
        }
        #region IScrollInfo Members

        public bool CanHorizontallyScroll
        {
            get
            {
                return (false);
            }
            set
            {
            }
        }

        private bool canVScroll;

        public bool CanVerticallyScroll
        {
            get
            {
                return (canVScroll);
            }
            set
            {
                canVScroll = value;
            }
        }

        public double ExtentHeight
        {
            get
            {
                return (extent.Height);
            }
        }

        public double ExtentWidth
        {
            get
            {
                return (extent.Width);
            }
        }

        public double HorizontalOffset
        {
            get
            {
                return (0.0d);
            }
        }
        public void LineDown()
        {
            SetVerticalOffset(verticalOffset + 1);
        }
        public void LineLeft()
        {
        }
        public void LineRight()
        {
        }
        public void LineUp()
        {
            SetVerticalOffset(verticalOffset - 1);
        }
        public System.Windows.Rect MakeVisible(
          System.Windows.Media.Visual visual, System.Windows.Rect rectangle)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public void MouseWheelDown()
        {
            SetVerticalOffset(verticalOffset + itemHeight);
        }
        public void MouseWheelLeft()
        {
        }
        public void MouseWheelRight()
        {
        }
        public void MouseWheelUp()
        {
            SetVerticalOffset(verticalOffset - itemHeight);
        }
        public void PageDown()
        {
            SetVerticalOffset(verticalOffset + itemHeight);
        }
        public void PageLeft()
        {
        }
        public void PageRight()
        {
        }
        public void PageUp()
        {
            SetVerticalOffset(verticalOffset - itemHeight);
        }
        public ScrollViewer ScrollOwner
        {
            get
            {
                return (scrollOwner);
            }
            set
            {
                scrollOwner = value;
            }
        }

        public void SetHorizontalOffset(double offset)
        {
            // We don't horizontally scroll
        }

        public void SetVerticalOffset(double offset)
        {
            offset = Math.Max(0.0d, offset);

            offset = Math.Min(extent.Height - viewport.Height,
              offset);

            verticalOffset = offset;

            renderTransform.Y = 0 - verticalOffset;

            RealizeVisibleItems();

            if (scrollOwner != null)
            {
                scrollOwner.InvalidateScrollInfo();
            }
        }

        public double VerticalOffset
        {
            get
            {
                return (verticalOffset);
            }
        }

        public double ViewportHeight
        {
            get
            {
                return (viewport.Height);
            }
        }

        public double ViewportWidth
        {
            get
            {
                return (viewport.Width);
            }
        }

        #endregion

        private ScrollViewer scrollOwner;
        private Size extent;
        private Size viewport;
        private double verticalOffset;
        private TranslateTransform renderTransform;
        private double itemWidth;
        private double itemHeight;
        private readonly Size maxSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
    }
}
